using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;

namespace OpenLS.Spreadsheet
{
    internal class CellsLeftCurtain
    {
        private readonly Dictionary<int, Cell> _previousCells = new Dictionary<int, Cell>();
        private readonly Worksheet _worksheet;
        private int _columnIndex;

        public CellsLeftCurtain(Worksheet worksheet, IntInterval rowInterval, int columnIndex)
        {
            _worksheet = worksheet;
            _rowInterval = rowInterval;
            _columnIndex = -1;
            ColumnIndex = columnIndex;
            _worksheet.CellsRemoved += WorksheetCellsRemoved;
            _worksheet.CellsAdded += WorksheetCellsAdded;
            _worksheet.CellValueChanged += _worksheet_CellValueChanged;
#if DEBUG
            if (Application.Current != null)
                Application.Current.Exit += (sender, e) => GC.SuppressFinalize(this);
#endif
        }

        private void _worksheet_CellValueChanged(object sender, ValueChangedEventArgs<object> e)
        {
            if ((e.OldValue == null) == (e.NewValue == null))
                return;
            var cell = (Cell) sender;
            if (cell.Row.Index > _rowInterval.Last)
                return;
            if (cell.Row.Index + cell.RowSpan - 1 < _rowInterval.First)
                return;
            if (cell.Column.Index >= ColumnIndex)
                return;
            Cell oldCell;
            _previousCells.TryGetValue(cell.Row.Index, out oldCell);
            if (oldCell != cell)
                return;
            int rowIndex = cell.Row.Index;
            Cell newCell = GetPreviousCell(cell.Row.Index);
            Contract.Assert(newCell != oldCell);
            var added = new List<Cell>();
            _previousCells.Remove(rowIndex);
            if (newCell != null)
            {
                added.Add(newCell);

                _previousCells.Add(rowIndex, newCell);
            }

            var oldCells = new List<Cell> {oldCell};
            OnCellsRemoved(oldCells);
            OnCellsAdded(added);
        }

        public void Dispose()
        {
            _worksheet.CellsRemoved -= WorksheetCellsRemoved;
            _worksheet.CellsAdded -= WorksheetCellsAdded;
            _worksheet.CellValueChanged -= _worksheet_CellValueChanged;

#if DEBUG
            disposed = true;
#endif
        }

#if DEBUG
        private bool disposed;

        ~CellsLeftCurtain()
        {
            Contract.Assert(disposed);
        }
#endif

        private void WorksheetCellsAdded(object sender, ListEventArgs<Cell> e)
        {
            var added = new List<Cell>();
            var removed = new List<Cell>();
            foreach (Cell cell in e.Items)
            {
                if (cell.Row.Index > _rowInterval.Last)
                    continue;
                if (cell.Row.Index + cell.RowSpan - 1 < _rowInterval.First)
                    continue;
                if (cell.Column.Index >= ColumnIndex)
                    continue;
                if (cell.Value == null)
                    continue;
                //\\Contract.Assert((cell.Value != null));
                int rowIndex = cell.Row.Index;
                {
                    Cell oldCell;
                    _previousCells.TryGetValue(rowIndex, out oldCell);
                    if (oldCell == cell)
                        continue;
                    if (oldCell != null && oldCell.Column.Index > cell.Column.Index)
                        continue;
                    if (oldCell != null)
                        removed.Add(oldCell);
                    added.Add(cell);
                    _previousCells.Remove(rowIndex);
                    _previousCells.Add(rowIndex, cell);
                }
            }
            OnCellsAdded(added);
            OnCellsRemoved(removed);
        }

        private void OnCellsRemoved(List<Cell> removed)
        {
            if (CellsRemoved != null && removed.Count > 0)
                CellsRemoved(this, new CellListEventArgs(removed));
        }

        private void OnCellsAdded(List<Cell> added)
        {
            if (CellsAdded != null && added.Count > 0)
                CellsAdded(this, new CellListEventArgs(added));
        }

        private void WorksheetCellsRemoved(object sender, CellRegionEventArgs e)
        {
            var added = new List<Cell>();
            var removed = new List<Cell>();
            foreach (Cell cell in e.Region.GetCells(RangeAddress))
            {
                if (cell.Row.Index > _rowInterval.Last)
                {
                    // Contract.Assert(false);
                    continue;
                }
                if (cell.Row.Index + cell.RowSpan - 1 < _rowInterval.First)
                {
                    // Contract.Assert(false);
                    continue;
                }
                if (cell.Column.Index >= ColumnIndex)
                    continue;
                int rowIndex = cell.Row.Index;
                {
                    Cell oldCell = _previousCells[rowIndex]; //\\ rowspan
                    if (oldCell != null && oldCell.Column.Index > cell.Column.Index)
                        continue;
                    Contract.Assert(oldCell == cell);
                    removed.Add(oldCell);
                    added.Add(cell);
                    _previousCells.Remove(rowIndex);
                    _previousCells.Add(rowIndex, cell);
                }
            }
            OnCellsAdded(added);
            OnCellsRemoved(removed);
        }

        private RangeAddress RangeAddress
        {
            get
            {
                return new RangeAddress(new CellAddress(_rowInterval.First, ColumnIndex),
                                        new CellAddress(_rowInterval.Last, ColumnIndex));
            }
        }

        public int ColumnIndex
        {
            get { return _columnIndex; }
            set
            {
                if (_columnIndex == value)
                    return;
                var removed = new List<Cell>();
                var added = new List<Cell>();
                _columnIndex = value;
                foreach (int row in _rowInterval.GetValues())
                {
                    Cell oldCell;
                    _previousCells.TryGetValue(row, out oldCell);
                    Cell newCell = GetPreviousCell(row);
                    if (oldCell == newCell)
                        continue;
                    if (oldCell != null)
                        removed.Add(oldCell);
                    if (newCell != null)
                        added.Add(newCell);
                    _previousCells.Remove(row);
                    _previousCells.Add(row, newCell);
                }
                OnCellsAdded(added);
                OnCellsRemoved(removed);
            }
        }

        private IntInterval _rowInterval;

#if DEBUG
        private bool IsConsistent()
        {
            foreach (int key in _previousCells.Keys)
                Contract.Assert(_rowInterval.Contains(key));
            return true;
        }
#endif

        public IntInterval RowInterval
        {
            get { return _rowInterval; }
            set
            {
#if DEBUG
                Contract.Assert(IsConsistent());
                //    Contract.Ensures(IsConsistent());
#endif
                if (_rowInterval.Equals(value))
                    return;
                var removed = new List<Cell>();
                var added = new List<Cell>();
                if (!_rowInterval.Intersects(value))
                {
                    removed.AddRange(Enumerable.Where(_previousCells.Values, c => c != null));
                    Contract.Assert(Enumerable.All(removed, c => c != null));
                    _previousCells.Clear();
                    for (int i = value.First; i <= value.Last; i++)
                    {
                        Cell cell = GetPreviousCell(i);
                        _previousCells.Add(i, cell);
                        if (cell != null)
                            added.Add(cell);
                    }
                }
                else
                {
                    for (int i = _rowInterval.First; i < value.First; i++)
                    {
                        Cell cell;
                        if (_previousCells.TryGetValue(i, out cell))
                        {
                            if (cell != null)
                                removed.Add(cell);
                            _previousCells.Remove(i);
                        }
                    }
                    for (int i = value.Last + 1; i <= _rowInterval.Last; i++)
                    {
                        Cell cell;
                        if (_previousCells.TryGetValue(i, out cell))
                        {
                            if (cell != null)
                                removed.Add(cell);
                            _previousCells.Remove(i);
                        }
                    }
                    for (int i = value.First; i < _rowInterval.First; i++)
                    {
                        Cell cell = GetPreviousCell(i);
                        _previousCells.Add(i, cell);
                        if (cell != null)
                            added.Add(cell);
                    }
                    for (int i = _rowInterval.Last + 1; i <= value.First; i++)
                    {
                        Cell cell = GetPreviousCell(i);
                        _previousCells.Add(i, cell);
                        if (cell != null)
                            added.Add(cell);
                    }
                }
                if (removed.Count > 0)
                    CellsRemoved(this, new CellListEventArgs(removed));
                if (added.Count > 0)
                    CellsAdded(this, new CellListEventArgs(added));
                _rowInterval = value;
            }
        }

        public event EventHandler<CellListEventArgs> CellsAdded;
        public event EventHandler<CellListEventArgs> CellsRemoved;


        private Cell GetPreviousCell(int rowIndex)
        {
            Contract.Ensures(Contract.Result<Cell>() == null || Contract.Result<Cell>().Value != null);
            if (ColumnIndex == 0)
                return null;
            int columnCount = ColumnIndex;
            Row row = _worksheet.Rows[rowIndex];
            if (columnCount < row.Count)
            {
                Contract.Assert(columnCount == ColumnIndex);
                for (int i = columnCount - 1; i >= 0; i--)
                {
                    Contract.Assert(columnCount == ColumnIndex);
                    Cell cell = _worksheet.Cells.NonVolatileCells.GetItemIfAny(new CellAddress(rowIndex, i));
                    if (cell != null && cell.Value != null)
                        return cell;
                    Contract.Assert(columnCount == ColumnIndex);
                }
                return null;
            }
            Cell result = null;
            foreach (Cell cell in Enumerable.Where(row.GetCells(), c => c.Column.Index < columnCount))
            {
                if (cell.State == Cell.CellState.Volatile || cell.Value == null)
                    continue;
                if (result == null)
                    result = cell;
                else if (cell.Column.Index > result.Column.Index)
                    result = cell;
            }
            return result;
        }

        private IEnumerable<Cell> GetCells()
        {
            return Enumerable.Where(_previousCells.Values, c => c != null);
        }

        public IEnumerable<CellClusterKey> GetClusterKeys()
        {
            IEnumerable<CellClusterKey> clusters = Enumerable.Distinct(
                Enumerable.Select(
                    Enumerable.SelectMany(GetCells(), c => c.GetRangeAddress().GetCellAddresses()),
                    a => new CellClusterKey(a)));
            return clusters;
        }
    }
}